use bytes::{BufMut, BytesMut};
use smallvec::SmallVec;

pub type CommandArgs = SmallVec<[Vec<u8>; 8]>;

pub trait ToCommandArgs {
    fn write(&self, args: &mut CommandArgs);
}

impl ToCommandArgs for &str {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.as_bytes().to_vec());
    }
}

impl ToCommandArgs for &String {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.as_bytes().to_vec());
    }
}

impl ToCommandArgs for String {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.as_bytes().to_vec());
    }
}

impl ToCommandArgs for u8 {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.to_string().as_bytes().to_vec());
    }
}

impl ToCommandArgs for u16 {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.to_string().as_bytes().to_vec());
    }
}

impl ToCommandArgs for u32 {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.to_string().as_bytes().to_vec());
    }
}

impl ToCommandArgs for u128 {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.to_string().as_bytes().to_vec());
    }
}

impl ToCommandArgs for &faststr::FastStr {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.to_string().as_bytes().to_vec())
    }
}

impl ToCommandArgs for isize {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.to_string().as_bytes().to_vec())
    }
}

impl ToCommandArgs for usize {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.to_string().as_bytes().to_vec())
    }
}

impl ToCommandArgs for f32 {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.to_string().as_bytes().to_vec())
    }
}

impl ToCommandArgs for f64 {
    fn write(&self, args: &mut CommandArgs) {
        args.push(self.to_string().as_bytes().to_vec())
    }
}

pub struct Command<'a> {
    pub name: &'a str,

    pub key: Option<&'a str>,

    pub keys: Option<&'a [&'a str]>,

    pub args: CommandArgs,
}

impl<'a> Command<'a> {
    pub fn new(name: &'a str) -> Command<'_> {
        Self {
            name,
            key: None,
            keys: None,
            args: SmallVec::new(),
        }
    }

    pub fn new_with_key(name: &'a str, key: &'a str) -> Command<'a> {
        Self {
            name,
            key: Some(key),
            keys: None,
            args: SmallVec::new(),
        }
    }

    pub fn new_with_keys(name: &'a str, keys: &'a [&'a str]) -> Command<'a> {
        Self {
            name,
            key: None,
            keys: Some(keys),
            args: SmallVec::new(),
        }
    }

    pub fn new_with_key_and_keys(name: &'a str, key: &'a str, keys: &'a [&'a str]) -> Command<'a> {
        Self {
            name,
            key: Some(key),
            keys: Some(keys),
            args: SmallVec::new(),
        }
    }

    #[inline]
    pub fn arg<T: ToCommandArgs>(mut self, arg: T) -> Self {
        arg.write(&mut self.args);
        self
    }

    #[inline]
    pub fn arg_append<T: ToCommandArgs>(&mut self, arg: T) -> &mut Self {
        arg.write(&mut self.args);
        self
    }

    #[inline]
    pub fn arg_if<T: ToCommandArgs>(&mut self, condition: bool, arg: T) -> &mut Self {
        if condition {
            arg.write(&mut self.args);
        }
        self
    }

    pub fn get_key(&self) -> Option<&str> {
        self.key
    }

    pub fn encode(&self) -> BytesMut {
        let mut buf = BytesMut::new();
        buf.put_u8(b'*');
        if let Some(key) = self.key {
            if let Some(keys) = self.keys {
                encode_size(self.args.len() + 2 + keys.len(), &mut buf);
                encode_crlf(&mut buf);
                encode_bulkstring(self.name.as_bytes(), &mut buf);
                encode_bulkstring(key.as_bytes(), &mut buf);
                for m_key in keys {
                    encode_bulkstring(m_key.as_bytes(), &mut buf);
                }
            } else {
                encode_size(self.args.len() + 2, &mut buf);
                encode_crlf(&mut buf);
                encode_bulkstring(self.name.as_bytes(), &mut buf);
                encode_bulkstring(key.as_bytes(), &mut buf);
            }
        } else {
            if let Some(keys) = self.keys {
                encode_size(self.args.len() + 1 + keys.len(), &mut buf);
                encode_crlf(&mut buf);
                encode_bulkstring(self.name.as_bytes(), &mut buf);
                for m_key in keys {
                    encode_bulkstring(m_key.as_bytes(), &mut buf);
                }
            } else {
                encode_size(self.args.len() + 1, &mut buf);
                encode_crlf(&mut buf);
                encode_bulkstring(self.name.as_bytes(), &mut buf);
            }
        }
        for arg in self.args.iter() {
            encode_bulkstring(&arg, &mut buf);
        }
        buf
    }
}

#[inline]
fn encode_bulkstring(str: &[u8], buf: &mut BytesMut) {
    buf.put_u8(b'$');
    encode_size(str.len(), buf);
    encode_crlf(buf);
    buf.put(str);
    encode_crlf(buf);
}

#[inline]
fn encode_size(size: usize, buf: &mut BytesMut) {
    // let mut buffer = itoa::Buffer::new();
    // let str = buffer.format(size);
    // buf.put(str.as_bytes());
    buf.put(size.to_string().as_bytes())
}

#[inline]
fn encode_crlf(buf: &mut BytesMut) {
    buf.put(&b"\r\n"[..]);
}
